/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package raytracer;

import java.awt.Color;
import java.awt.image.BufferedImage;
import javax.xml.crypto.dsig.spec.C14NMethodParameterSpec;

/**
 *
 * @author ferco
 */
public class RayCaster {

    //deklarace promennych
    private BufferedImage screen;   //slouzi k ukladani barev z paprsku
    private int width;              //sirka obrazovky
    private int height;             //vyska obrazovky
    private double distance;        //vzdalenost pozorovatele od obrazovky       
    private Scene scene;
    private Vector3D eyePosition;
    private int FOV = 60;
    
    

    /**
     * Konstruktor RayCasteru, nastavi promenne a vypocita vzdalenost obrazovky
     * od pozorovatele
     * @param screen
     * @param scene
     */
    public RayCaster(BufferedImage screen, Scene scene) {

        this.screen = screen;
        this.scene = scene;
        this.width = screen.getWidth();
        this.height = screen.getHeight();

        //ziskano z http://www.ceske-hry.cz/forum/viewtopic.php?t=1420&postdays=0&postorder=asc&start=0 = -689.4835294934037
        this.distance = (this.height / 2) / Math.tan(Math.PI - 0.00872664625997164788461845384244 * FOV);
        //distance = 1;
        //nastaveni pozorovatele
        eyePosition = new Vector3D(0.0, 0.0, 5);
        System.out.println(distance);
        
    }

//    float srgbEncode(float c)
//{
//    if (c <= 0.0031308f)
//    {
//        return 12.92f * c;
//    }
//    else
//    {
//        return 1.055f * powf(c, 0.4166667f) - 0.055f; // Inverse gamma 2.4
//    }
//}
////..
// // gamma correction
// output.blue = srgbEncode(output.blue);
// output.red = srgbEncode(output.red);
// output.green = srgbEncode(output.green);

    public double ergbEncode(double c){

        if (c <= 0.0031308){
            return 12.92f * c;
        } else {
            return 1.055f * Math.pow(c, 0.4166667f) - 0.055f; // Inverse gamma 2.4
        }
    }

    /**
     * metoda slouzici k vystreleni paprsku skrz kazdy pixel obrazovky
     * TODO..vystrelovani vice paprsku skrz jeden pixel
     */
    public void castRays() {        
        for (int i = 0; i < this.width ; i++) {
            for (int j = 0; j < this.height ; j++) {
                screen.setRGB(i, j, this.castPrimaryRay(i, j));
            }
        }
    }

    /**
     * metoda pro vystreleni prvni paprsku (primary rays), tyto paprsky maji pocatek
     * na pozici pozorovatele a smer je urcen polohou pixelu na obrazovce
     * @param x     Xova pozice piselu
     * @param y     Yova pozice pixelu
     * @return      barvu pixelu na pozici x a y
     */
    public int castPrimaryRay(int x, int y) {
        int AA = 4;
        //vypocteni smeru parsku

        Vector3D[] direction = new Vector3D[AA * AA+1];
        
        for (float i = 0; i < AA; i++){
            for (float j = 0; j < AA; j++){
                //System.out.println(j/AA + " , " + i/AA);
                direction[(int)(i*AA+j)] = new Vector3D((x - this.width / 2) - 0.5 + j/AA , (y - this.height / 2) - 0.5 + i/AA, distance);
                direction[(int)(i*AA+j)].normalize();
            }
        }

        Color[] color = new Color[AA * AA];
        float[] array = new float[4];
        float[] colorFloat = new float[3];
        colorFloat[0]= 0;
        colorFloat[1]= 0;
        colorFloat[2]= 0;

        for(int i = 0; i < AA * AA; i++){
           
            color[i] = castRay(eyePosition, direction[i], 5);
            color[i].getColorComponents(array);
            for(int j = 0; j<3; j++){
                array[j] /= (AA * AA);
            }
            //color[i] = new Color(array[0], array[1], array[2]);
            for(int k = 0; k<3; k++){
                colorFloat[k] += array[k];
            }
        }

//        return castRay(eyePosition, direction, 3).getRGB();

//       Color colorRet = new Color(  Math.min(255, (color[0].getRed() + color[1].getRed() + color[2].getRed() + color[3].getRed())),
//                                    Math.min(255, (color[0].getGreen() + color[1].getGreen() + color[2].getGreen() + color[3].getGreen())),
//                                    Math.min(255, (color[0].getBlue() + color[1].getBlue() + color[2].getBlue() + color[3].getBlue())));
        return new Color(Math.min(255,colorFloat[0]), Math.min(255,colorFloat[1]), Math.min(255,colorFloat[2])).getRGB();

    }

    /**
     * Metoda slozici k vrhnuti jednoho parsku s pocatkem v origin a smerem direction,
     * parametr recursionDepth udava hloubku rekurye tudis pocet odrazu paprsku
     * @param origin            pocatek paprsku
     * @param direction         smer paprsku
     * @param recursionDepth    maximalni pocet odrazu paprsku
     * @return
     */
    public Color castRay(Vector3D origin, Vector3D direction, int recursionDepth) {

        //pokud jsme dosahli stanoveneho poctu paprsku vrat cernou barvu
        if (recursionDepth < 0){
            return Color.black;
        }

        // vrhni novy paprsek
        Ray ray = new Ray(origin, direction);

        //vytvor novy pomocny bod pruniku
        Intersection intersection;

        //pro kazdy objekt ve scene zjisti jestli jej paprsek protina
        for (Primitive p : scene.getPrimitives()) {
           intersection = p.findGeometricIntersection(ray);
          // intersection = p.findIntersection(ray);
            if (intersection != null) {
                //pokud paprsek objekt protina, zjisti jestli je to nejblizsi
                    //prusecik, pokud ano, uloz ho do paprsku
                ray.setIntersection(intersection.closerIntersection(ray.getIntersection()));
                //System.out.println(intersection.getPrimitive().getLocation());
            }
        }

        //uloz do intersection bod pruniku s nejmensi vzdalenosti od pocatku paprsku
        intersection = ray.getIntersection();

            //pokud takovy bod existuje pokracuj ve vypoctu barvy, v opacnem pripade
                //vrat predeslou bravu paprsku
            if (ray.getIntersection()!=null) {
                //ziskej objekt se kterym ma paprsek prusecik
                Primitive p = intersection.getPrimitive();
                //nastav barvu paprsku podle barvy objektu
                ray.setColor(p.getMaterial().getColor());
                //vypocitej normalu v pruseciku
                Vector3D normal = p.calculateNormal(intersection.getPoint());

//                Vector3D materialAmbient = new Vector3D(0.8, 0.8, 0.8);
//                Vector3D materialDiffuse = new Vector3D(0.7, 0.7, 0.7);
//                Vector3D materialSpecular = new Vector3D(0.6, 0.6, 0.6);
                Vector3D materialAmbient = new Vector3D(ray.getColor().getRed(), ray.getColor().getGreen(), ray.getColor().getBlue());
                Vector3D materialDiffuse = new Vector3D(ray.getColor().getRed(), ray.getColor().getGreen(), ray.getColor().getBlue());
                Vector3D materialSpecular = new Vector3D(255,255,255);
                Vector3D lightColor = new Vector3D(1, 1, 1);
                float shadow = 1;
                ray.setColor(p.getMaterial().getColor());
                for (Light l : scene.getLights()){


                    Vector3D vectorToLight = l.getPossition().minusVector(intersection.getPoint());
                    vectorToLight.normalize();
                    Vector3D vectorFromLight = intersection.getPoint().minusVector(l.getPossition());
                    vectorFromLight.normalize();
                    Vector3D viewVector = ray.getDirection();//intersection.getPoint().minusVector(ray.getOrigin());
                    viewVector.normalize();
                   // Vector3D reflectLightVector = vectorToLight.minusVector(normal.multiplyBy(2*vectorToLight.dot(normal)));
                    Vector3D reflectLightVector = vectorToLight.reflectVector(normal);
                    reflectLightVector.normalize();

                    Ray shadowRay = new Ray(intersection.getPoint(), vectorToLight);
                    Intersection shadowIntersection;
                    for (Primitive sp : scene.getPrimitives()) {
                             shadowIntersection = sp.findGeometricIntersection(shadowRay);
                            if (shadowIntersection != null&& !sp.getLocation().equals(p.getLocation())) {
                           shadow = 0.5f;
                           break;
                        }
                    }

                    Vector3D ambient = new Vector3D(materialAmbient.getX()*lightColor.getX()*0.4,
                                                    materialAmbient.getY()*lightColor.getY()*0.4,
                                                    materialAmbient.getZ()*lightColor.getZ()*0.4);

                    double diffCoef = vectorToLight.dot(normal);
                    if (diffCoef < 0){
                        diffCoef = 0;
                    }

                    Vector3D diffuse = new Vector3D(materialDiffuse.getX()*lightColor.getX() * diffCoef,
                                                    materialDiffuse.getY()*lightColor.getY() * diffCoef,
                                                    materialDiffuse.getZ()*lightColor.getZ() * diffCoef);

                    double specDot = reflectLightVector.dot(viewVector);
                        if (specDot < 0){
                            specDot = 0;
                        }
                    double specCoef = Math.pow(specDot, 40);
                   // specCoef = l.calculateSpecular(ray, vectorToLight, normal);
                    Vector3D specular = new Vector3D(materialSpecular.getX()*lightColor.getX() * specCoef,
                                                     materialSpecular.getY()*lightColor.getY() * specCoef,
                                                     materialSpecular.getZ()*lightColor.getZ() * specCoef);


                    Vector3D color = diffuse.plusVector(specular).plusVector(ambient);//ambient.plusVector(diffuse);//.plusVector(Specular);
                   // Vector3D color = ambient;
                    //Vector3D color = diffuse.plusVector(ambient);
                   // Vector3D color = specular;
                    //System.out.println(color.toString());



                    int r = Math.min(255,(int) (color.getX()  * shadow ));
                    int g = Math.min(255,(int) ( color.getY() * shadow));
                    int b = Math.min(255,(int) ( color.getZ() * shadow ));

                    ray.setColor(new Color(r,g,b));
                }

//                //vypocitej slozku osvetleni v pruseciku
//                //pro kazde svetlo ve scene
//                for (Light l : scene.getLights()) {
//
////                    double x = l.calculateDiffuse(intersection.getPoint(), normal) +
////                                l.calculateSpecular(ray, intersection.getPoint(), normal);
////                    ray.modifyColor(x);
//                    Vector3D lightPosition = new Vector3D(  p.getLocation().getX(),
//                                                            p.getLocation().getY(),
//                                                            p.getLocation().getZ()+1);
//
//
//                    //lightPosition = l.getPossition();
//                     Vector3D vectorToLight = lightPosition.minusVector(intersection.getPoint());
//                    double spec = l.calculateSpecular(ray, vectorToLight, normal);
//                    double diff = l.calculateDiffuse(vectorToLight, normal);
//                    double shadow = l.calculateShadow(new Ray(intersection.getPoint(), vectorToLight), scene);
//                    shadow = 1;
//                   // spec = 0;
//                    int r = Math.min(255,(int) (shadow * (ray.getColor().getRed() + (ray.getColor().getRed() * diff + spec))));
//                    int g = Math.min(255,(int) (shadow * (ray.getColor().getGreen() +  (ray.getColor().getGreen()* diff + spec))));
//                    int b = Math.min(255,(int) (shadow * (ray.getColor().getBlue() + (ray.getColor().getBlue() * diff + spec))));
//
//                    ray.setColor(new Color(r,g,b));
//                }

                //jakmile je vypocitana barva, vypocitame odraz
                //vypocitej smer odrazu pomoci normaly
                Vector3D revDir = direction.multiplyBy(-1);
                Vector3D reflectVector = direction.reflectVector(normal);
                reflectVector.normalize();
                //vrhni novy paprsek s pocatkem v pode pruniku a smerem odrazu,
                    //zapamatuj si barvu kterou ziska
                Color color = castRay(intersection.getPoint(), reflectVector, recursionDepth - 1);
                //pricti barvu odrazeneho paprsku k barve pruseciku,
                    //pocita se az na ceste "zpet"
                double ind = 0.4;
                int r = Math.min(255, (int) (ray.getColor().getRed() + color.getRed() * ind));
                int g = Math.min(255, (int) (ray.getColor().getGreen() + color.getGreen()* ind));
                int b = Math.min(255, (int) (ray.getColor().getBlue() + color.getBlue()* ind));
                //nastav upravenou barvu jako barvu paprsku
                ray.setColor(new Color(r, g, b));

//                //vypocet lomeneho parsku
//                double gama, sqrt, dot, index;
//                index = 1.5;
//                Vector3D revDir = direction.multiplyBy(-1);
//                dot = normal.dot(revDir);
//
//                if(dot > 0){
//                    gama = 1/index;
//                } else {
//                    gama = index;
//                    dot = -dot;
//                    normal = normal.multiplyBy(-1);
//                }
//                sqrt = 1 - gama * gama * (1 - dot * dot);
//                if(sqrt > 0){
//                    sqrt = gama * dot - Math.sqrt(sqrt);
//                    Ray transmittedRay = new Ray(intersection.getPoint(), normal.multiplyBy(sqrt).plusVector(ray.getDirection().multiplyBy(gama)));
//                    Color color = castRay(intersection.getPoint(), normal.multiplyBy(sqrt).plusVector(ray.getDirection().multiplyBy(gama)), recursionDepth - 1);
//               //pricti barvu odrazeneho paprsku k barve pruseciku,
//                    //pocita se az na ceste "zpet"
//                int r = Math.min(255, (int) (ray.getColor().getRed() + color.getRed() ));
//                int g = Math.min(255, (int) (ray.getColor().getGreen() + color.getGreen()));
//                int b = Math.min(255, (int) (ray.getColor().getBlue() + color.getBlue()));
//                //nastav upravenou barvu jako barvu paprsku
//                ray.setColor(new Color(r, g, b));
//                }
            }
        //vrat barvu paprsku
        return ray.getColor();
    }

}
